package cn.site.core.orm.persistence;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.internal.CriteriaImpl;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.transform.Transformers;

import cn.site.core.utils.Reflections;
import cn.site.core.utils.StringUtils;

/**
 * Created by ZGJ on 2015/3/3.
 */
@SuppressWarnings({"unchecked","rawtypes"})
public class BaseDaoImpl<T> implements BaseDao<T> {
    // 获取当前线程的 EntityManager 实例
    @PersistenceContext
    private EntityManager entityManager;

    private Class<?> entityClass;

    public BaseDaoImpl() {
        this.entityClass = Reflections.getClassGenricType(super.getClass());
    }

    public EntityManager getEntityManager() {
        return this.entityManager;
    }

    public Session getSession() {
        return ((Session) getEntityManager().getDelegate());
    }

    public void flush() {
        getSession().flush();
    }

    public void clear() {
        getSession().clear();
    }

    
	public <E> Page<E> find(Page<E> page, String qlString, Object[] parameter) {
        if ((!(page.isDisabled())) && (!(page.isNotCount()))) {
            String countQlString = "select count(*) "
                    + removeSelect(removeOrders(qlString));

            Query query = createQuery(countQlString, parameter);
            List list = query.list();
            if (list.size() > 0)
                page.setCount(Long.valueOf(list.get(0).toString()).longValue());
            else {
                page.setCount(list.size());
            }
            if (page.getCount() < 1L) {
                return page;
            }
        }

        String ql = qlString;
        if (StringUtils.isNotBlank(page.getOrderBy())) {
            ql = ql + " order by " + page.getOrderBy();
        }
        Query query = createQuery(ql, parameter);

        if (!(page.isDisabled())) {
            query.setFirstResult(page.getFirstResult());
            query.setMaxResults(page.getMaxResults());
        }
        page.setList(query.list());
        return page;
    }

	public <E> List<E> find(String qlString, Object[] parameter) {
        Query query = createQuery(qlString, parameter);
        return query.list();
    }

    public int update(String qlString, Object[] parameter) {
        return createQuery(qlString, parameter).executeUpdate();
    }

    public Query createQuery(String qlString, Object[] parameter) {
        Query query = getSession().createQuery(qlString);
        setParameter(query, parameter);
        return query;
    }

    public <E> Page<E> findBySql(Page<E> page, String sqlString,
                                 Object[] parameter) {
        return findBySql(page, sqlString, null, parameter);
    }

    public <E> Page<E> findBySql(Page<E> page, String sqlString,
                                 Class<?> resultClass, Object[] parameter) {
        if ((!(page.isDisabled())) && (!(page.isNotCount()))) {
            String countSqlString = "select count(*) "
                    + removeSelect(removeOrders(sqlString));

            Query query = createSqlQuery(countSqlString, parameter);
            List list = query.list();
            if (list.size() > 0)
                page.setCount(Long.valueOf(list.get(0).toString()).longValue());
            else {
                page.setCount(list.size());
            }
            if (page.getCount() < 1L) {
                return page;
            }
        }

        String sql = sqlString;
        if (StringUtils.isNotBlank(page.getOrderBy())) {
            sql = sql + " order by " + page.getOrderBy();
        }
        SQLQuery query = createSqlQuery(sql, parameter);

        if (!(page.isDisabled())) {
            query.setFirstResult(page.getFirstResult());
            query.setMaxResults(page.getMaxResults());
        }
        setResultTransformer(query, resultClass);
        page.setList(query.list());
        return page;
    }

    public <E> List<E> findBySql(String sqlString, Object[] parameter) {
        return findBySql(sqlString, null, parameter);
    }

    public <E> List<E> findBySql(String sqlString, Class<?> resultClass,
                                 Object[] parameter) {
        SQLQuery query = createSqlQuery(sqlString, parameter);
        setResultTransformer(query, resultClass);
        return query.list();
    }

    public int updateBySql(String sqlString, Object[] parameter) {
        return createSqlQuery(sqlString, parameter).executeUpdate();
    }

    public SQLQuery createSqlQuery(String sqlString, Object[] parameter) {
        SQLQuery query = getSession().createSQLQuery(sqlString);
        setParameter(query, parameter);
        return query;
    }

    private void setResultTransformer(SQLQuery query, Class<?> resultClass) {
        if (resultClass != null)
            if (resultClass == Map.class)
                query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
            else if (resultClass == List.class)
                query.setResultTransformer(Transformers.TO_LIST);
            else
                query.addEntity(resultClass);
    }

    private void setParameter(Query query, Object[] parameter) {
        if (parameter != null)
            for (int i = 0; i < parameter.length; ++i)
                query.setParameter(i, parameter[i]);
    }

    private String removeSelect(String qlString) {
        int beginPos = qlString.toLowerCase().indexOf("from");
        return qlString.substring(beginPos);
    }

    private String removeOrders(String qlString) {
        Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*", 2);
        Matcher m = p.matcher(qlString);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            m.appendReplacement(sb, "");
        }
        m.appendTail(sb);
        return sb.toString();
    }

    public Page<T> find(Page<T> page) {
        return find(page, createDetachedCriteria(new Criterion[0]));
    }

    public Page<T> find(Page<T> page, DetachedCriteria detachedCriteria) {
        return find(page, detachedCriteria, Criteria.DISTINCT_ROOT_ENTITY);
    }

    public Page<T> find(Page<T> page, DetachedCriteria detachedCriteria,
                        ResultTransformer resultTransformer) {
        if ((!(page.isDisabled())) && (!(page.isNotCount()))) {
            page.setCount(count(detachedCriteria));
            if (page.getCount() < 1L) {
                return page;
            }
        }
        Criteria criteria = detachedCriteria
                .getExecutableCriteria(getSession());
        criteria.setResultTransformer(resultTransformer);

        if (!(page.isDisabled())) {
            criteria.setFirstResult(page.getFirstResult());
            criteria.setMaxResults(page.getMaxResults());
        }

        if (StringUtils.isNotBlank(page.getOrderBy())) {
            for (String order : StringUtils.split(page.getOrderBy(), ",")) {
                String[] o = StringUtils.split(order, " ");
                if (o.length == 1)
                    criteria.addOrder(Order.asc(o[0]));
                else if (o.length == 2) {
                    if ("DESC".equals(o[1].toUpperCase()))
                        criteria.addOrder(Order.desc(o[0]));
                    else {
                        criteria.addOrder(Order.asc(o[0]));
                    }
                }
            }
        }
        page.setList(criteria.list());
        return page;
    }

    public List<T> find(DetachedCriteria detachedCriteria) {
        return find(detachedCriteria, Criteria.DISTINCT_ROOT_ENTITY);
    }

    public List<T> find(DetachedCriteria detachedCriteria,
                        ResultTransformer resultTransformer) {
        Criteria criteria = detachedCriteria
                .getExecutableCriteria(getSession());
        criteria.setResultTransformer(resultTransformer);
        return criteria.list();
    }

    public long count(DetachedCriteria detachedCriteria) {
        Criteria criteria = detachedCriteria
                .getExecutableCriteria(getSession());
        long totalCount = 0L;
        try {
            Field field = CriteriaImpl.class.getDeclaredField("orderEntries");
            field.setAccessible(true);
            List orderEntrys = (List) field.get(criteria);

            field.set(criteria, new ArrayList());

            criteria.setProjection(Projections.rowCount());
            totalCount = Long.valueOf(criteria.uniqueResult().toString())
                    .longValue();

            criteria.setProjection(null);

            field.set(criteria, orderEntrys);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return totalCount;
    }

    public DetachedCriteria createDetachedCriteria(Criterion[] criterions) {
        DetachedCriteria dc = DetachedCriteria.forClass(this.entityClass);
        for (Criterion c : criterions) {
            dc.add(c);
        }
        return dc;
    }
}
